/*
 * Copyright (c) 2011
 * UNPUBLISHED PROPRIETARY SOURCE CODE.
 */

package com.webservice.controllers;

import com.webservice.domain.ErrorResponse;
import com.webservice.domain.User;
import com.webservice.domain.UserPhoto;
import com.webservice.domain.dto.UserDto;
import com.webservice.domain.dto.UserLoginDto;
import com.webservice.enums.ErrorCode;
import com.webservice.exception.EmailAlreadyInUseException;
import com.webservice.exception.NicknameAlreadyInUseException;
import com.webservice.exception.TokenAlreadyInUseException;
import com.webservice.exception.UserPhotoNotFoundException;
import com.webservice.exception.user.InvalidPasswordException;
import com.webservice.exception.user.SignInNotFoundException;
import com.webservice.exception.user.UserNotFoundException;
import com.webservice.service.IEmailService;
import com.webservice.service.IUserPhotoService;
import com.webservice.service.IUserService;
import com.webservice.utils.ServiceUtils;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.env.Environment;
import org.springframework.security.crypto.keygen.KeyGenerators;
import org.springframework.security.crypto.keygen.StringKeyGenerator;
import org.springframework.stereotype.Controller;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.annotation.*;

import javax.inject.Inject;
import javax.servlet.http.HttpServletResponse;
import javax.validation.Valid;
import java.io.IOException;
import java.io.OutputStream;
import java.util.HashMap;
import java.util.Map;

/**
 * Main User management Service controller.
 *
 * @author vkolodrevskiy
 */
@Controller
public class UserServiceController {
    private static final Logger logger = LoggerFactory.getLogger(UserServiceController.class);

    private final IUserService userService;
    private final IUserPhotoService userPhotoService;
    private final IEmailService emailService;
    private final Environment environment;

	private final StringKeyGenerator tokenGenerator = KeyGenerators.string();

    @Inject
    public UserServiceController(IUserService userService, IUserPhotoService userPhotoService,
                                 IEmailService emailService, Environment environment) {
        this.userService = userService;
        this.userPhotoService = userPhotoService;
        this.emailService = emailService;
        this.environment = environment;
    }

    @RequestMapping(value="/services/user/register/", method=RequestMethod.POST, produces="application/json")
    public @ResponseBody Map registerUser(@Valid UserDto userDto, BindingResult result) {
        Map<String, Object> response = new HashMap<String, Object>();

        if (result.hasErrors()) {
            response.put("error", ServiceUtils.getErrorsList(result));
        } else {
            try {
                userDto.setToken(tokenGenerator.generateKey());
                User u = userService.createUser(dtoToUser(userDto));

                if(userDto.getPhotoFile() != null && !userDto.getPhotoFile().getOriginalFilename().isEmpty()) {
                    try {
                        UserPhoto userPhoto = new UserPhoto();
                        userPhoto.setUserId(u.getId());
                        userPhoto.setContent(userDto.getPhotoFile().getBytes());
                        userPhoto.setContentType(userDto.getPhotoFile().getContentType());
                        userPhotoService.createOrUpdateUserPhoto(userPhoto);
                    } catch (IOException e) {
                        logger.error("Failed to save user photo.", e);
                    }
                }

                emailService.sendWelcomeEmail(u);
                response.put("id", u.getToken());
                logger.info("User with token = [" + u.getToken() + "] was registered.");
            } catch (NicknameAlreadyInUseException e) {
                response.put("error", new ErrorResponse(ErrorCode.NICKNAME_ALREADY_IN_USE.getCode(), ErrorCode.NICKNAME_ALREADY_IN_USE.getMessage() + " Nickname = [" + userDto.getNickname() + "]."));
            } catch (EmailAlreadyInUseException e) {
                response.put("error", new ErrorResponse(ErrorCode.EMAIL_ALREADY_IN_USE.getCode(), ErrorCode.EMAIL_ALREADY_IN_USE.getMessage() + " Email = [" + userDto.getEmail() + "]."));
            } catch (TokenAlreadyInUseException e) {
                response.put("error", new ErrorResponse(ErrorCode.TOKEN_ALREADY_IN_USE.getCode(), ErrorCode.TOKEN_ALREADY_IN_USE.getMessage() + " Token = [" + userDto.getToken() + "]."));
            }
        }

        return response;
    }

    @RequestMapping(value="/services/user/edit/", method=RequestMethod.POST, produces="application/json")
    public @ResponseBody Map editUser(@Valid UserDto userDto, BindingResult result) {
        Map<String, Object> response = new HashMap<String, Object>();

        if (result.hasErrors()) {
            response.put("error", ServiceUtils.getErrorsList(result));
        } else {
            try {
                if(StringUtils.isEmpty(userDto.getId()) || StringUtils.length(userDto.getId()) > 16) {
                    result.addError(new FieldError("id", "id", "length must be between 1 and 16"));
                    result.addError(new FieldError("id", "id", "may not be empty"));
                    response.put("error", ServiceUtils.getErrorsList(result));
                }
                else {
                    // coz on ui parameter is called "id"
                    userDto.setToken(userDto.getId());

                    User u = userService.updateUser(dtoToUser(userDto));

                    if(userDto.getPhotoFile() != null && !userDto.getPhotoFile().getOriginalFilename().isEmpty()) {
                        try {
                            UserPhoto userPhoto = new UserPhoto();
                            userPhoto.setUserId(u.getId());
                            userPhoto.setContent(userDto.getPhotoFile().getBytes());
                            userPhoto.setContentType(userDto.getPhotoFile().getContentType());
                            userPhotoService.createOrUpdateUserPhoto(userPhoto);
                        } catch (IOException e) {
                            logger.error("Failed to update user photo.", e);
                        }
                    }

                    response.put("id", u.getToken());
                    logger.info("User with token = [" + u.getToken() + "] was updated.");
                }
            } catch (NicknameAlreadyInUseException e) {
                response.put("error", new ErrorResponse(ErrorCode.NICKNAME_ALREADY_IN_USE.getCode(), ErrorCode.NICKNAME_ALREADY_IN_USE.getMessage() + " Nickname = [" + userDto.getNickname() + "]."));
            } catch (EmailAlreadyInUseException e) {
                response.put("error", new ErrorResponse(ErrorCode.EMAIL_ALREADY_IN_USE.getCode(), ErrorCode.EMAIL_ALREADY_IN_USE.getMessage() + " Email = [" + userDto.getEmail() + "]."));
            } catch (UserNotFoundException e) {
                response.put("error", new ErrorResponse(ErrorCode.NO_SUCH_USER.getCode(), ErrorCode.NO_SUCH_USER.getMessage() + " Id = [" + userDto.getToken() + "]."));
            }
        }

        return response;
    }

    @RequestMapping(value="/services/user/login/", method=RequestMethod.GET, produces="application/json")
    public @ResponseBody Map login(@Valid UserLoginDto userLoginDto, BindingResult result) {
        Map<String, Object> response = new HashMap<String, Object>();

        if (result.hasErrors()) {
            response.put("error", ServiceUtils.getErrorsList(result));
        } else {
            try {
                User u = userService.login(userLoginDto.getEmail(), userLoginDto.getPassword());
                response.put("id", u.getToken());
                logger.info("User with token = [" + u.getToken() + "] logged in.");
            } catch (SignInNotFoundException e) {
                response.put("error", new ErrorResponse(ErrorCode.NO_SUCH_USER.getCode(), ErrorCode.NO_SUCH_USER.getMessage() + " Email = [" + userLoginDto.getEmail() + "]."));
            } catch (InvalidPasswordException e) {
                response.put("error", new ErrorResponse(ErrorCode.INVALID_PASSWORD.getCode(), ErrorCode.INVALID_PASSWORD.getMessage() + " Password = [" + userLoginDto.getPassword() + "]."));
            }
        }

        return response;
    }

    @RequestMapping(value="/services/user/get/", method=RequestMethod.GET, produces="application/json")
    public @ResponseBody Object getUserData(@RequestParam String id) {
        try {
            User user = userService.findUserByToken(id);
            logger.info("Get user data by token = [" + id + "]");
            return userToDto(user);
        } catch (UserNotFoundException e) {
            Map<String, ErrorResponse> response = new HashMap<String, ErrorResponse>();
            response.put("error", new ErrorResponse(ErrorCode.NO_SUCH_USER.getCode(), ErrorCode.NO_SUCH_USER.getMessage() + " Id = [" + id + "]."));
            return response;
        }
    }

	@RequestMapping(value="/services/user/delete/", method=RequestMethod.GET)
	public String deleteUser(@RequestParam String id) {
        userService.removeUserByToken(id);
        return "deleteUserConfirmation";
	}

    @RequestMapping("/services/user/photo/{userId}")
    public String getUserPhoto(@PathVariable("userId") String userId,
                               HttpServletResponse response) {
        try {
            User user = userService.findUserByToken(userId);
            UserPhoto photo = userPhotoService.findPhotoByUserId(user.getId());

            // if want to force browser save photo
            // response.setHeader("Content-Disposition", "inline;filename=\"" + photo.getFilename() + "\"");

            OutputStream out = response.getOutputStream();
            response.setContentType(photo.getContentType());
            out.write(photo.getContent());
            out.flush();
            out.close();

        } catch (UserNotFoundException e) {
            logger.error("Error while streaming image.", e);
        } catch (UserPhotoNotFoundException e) {
            logger.error("Error while streaming image.", e);
        } catch (IOException e) {
            logger.error("Error while streaming image.", e);
        }

        return null;
    }


    // ------------------------------------------------------------------------
    private User dtoToUser(UserDto userDto) {
        User user = new User();

        user.setBirthday(userDto.getBirthday());
        user.setEmail(userDto.getEmail());
        user.setFirstname(userDto.getFirstname());
        user.setGenre(userDto.getGenre());
        user.setLastname(userDto.getLastname());
        user.setNickname(userDto.getNickname());
        user.setPassword(userDto.getPassword());
        user.setToken(userDto.getToken());

        return user;
    }

    private UserDto userToDto(User user) {
        UserDto userDto = new UserDto();

        userDto.setBirthday(user.getBirthday());
        userDto.setEmail(user.getEmail());
        userDto.setFirstname(user.getFirstname());
        userDto.setGenre(user.getGenre());
        userDto.setLastname(user.getLastname());
        userDto.setNickname(user.getNickname());
        userDto.setPassword(user.getPassword());
        userDto.setPhoto(getAppUrl() + "services/user/photo/" + user.getToken());
        userDto.setToken(user.getToken());

        return userDto;
    }

    private String getAppUrl() {
        return environment.getProperty("application.url");
    }
}